%{
#include <iostream>
#include <cstdio>
#include <map>
#include <deque>
#include <cstring>
#include "declarations.h"
#define print(id) cout << id << endl;
using namespace std;
extern int yylex();
extern FILE *yyin;
void yyerror(const char* s) { printf("Error of parser\n");}

symbol_table table;
int tmpId = 0;
int ifId = 0, forId = 0, whileId = 0, blockId = 0;
const char *tmpPre = "tmp";
struct values_struct temp2;
%}

%union
{	
	struct values_struct values;
}

%token VOID INT DOUBLE BOOL STRING INULL
%token WHILE FOR IF ELSE RETURN BREAK PRINT READINT READLINE
%token TRUE FALSE
%token LESSEQTHAN MOREEQTHAN  EQUALCOMP DIFFCOMP
%token AND OR
%token<values> CHEX CDEC CSTRING CDOUBLE
%token<values> ID 

%type<values> constant variable type expr valueL functionName ifCond startScope

%left '='
%left OR
%left AND
%left EQUALCOMP DIFFCOMP
%left '<' LESSEQTHAN '>' MOREEQTHAN
%left '+' '-'
%left '*' '/' '%'
%left '!'
%left '['

%nonassoc LOWER_THAN_ELSE
%nonassoc ELSE

%start program


%%

program:	decl
        | 	program  decl
        ;

decl:       variableDecl
        | 	functionDecl 
        ;

variableDecl:	variable ';'
        ;

variable: 	type ID
			{ 
				if(!findKey(table, string($2.name))) 
				{
					$$.type = $1.type;
					printf("\tvar %s\n", $2.name);
					addSymbol(table, string($2.name), $2);
				}
				else
				{
					$$.type = error_type;
					cout << "ERROR: Variable already exists:  " << $2.name << endl;
					return 0;
				}				
			}
        |   type ID '[' CDEC ']'
			{ 
				if(!findKey(table, $2.name)) 
				{
					$$.type = $1.type;
					printf("\tvar %s[]\n", $2.name);
					addSymbol(table, $2.name, $2);
				}
				else
				{
					$$.type = error_type;
					cout << "ERROR: Array already exists: " << $2.name << endl;
					return 0;
				}			
			}
        ;

type:        INT 	{ $$.type = int_type; }
        |    DOUBLE { $$.type = double_type; }
        |    BOOL 	{ $$.type = bool_type; }
        |    STRING	{ $$.type = string_type; }
        ;

functionDecl:   functionName '(' formals ')' '{'  scopedBlockList '}' { printf("end_func_%s:\n", $1.name); popScope(table); }
		|    	functionName '(' ')' '{' scopedBlockList '}' { printf("end_func_%s:\n", $1.name); popScope(table); }
        ;

functionName: 	type ID	{ printf("begin_func_%s:\n", $2.name); strcpy($$.name, $2.name); pushScope(table); }
		|		VOID ID	{ printf("begin_func_%s:\n", $2.name); strcpy($$.name, $2.name); pushScope(table);}
		;

formals:    variable
        | 	formals ',' variable     
        ;

instBlockList: 	startScope instBlock1 instBlock2 '}' { popScope(table); printf("end_%s:\n", $1.name); } 
        | 		startScope '}' { popScope(table); printf("end_%s:\n", $1.name); }
        | 		startScope instBlock1 '}' { popScope(table); printf("end_%s:\n", $1.name); }
        | 		startScope instBlock2 '}' { popScope(table); printf("end_%s:\n", $1.name); }
        ;

scopedBlockList: instBlock1 instBlock2 
        |		/* empty */
       	|		instBlock1
       	|		instBlock2

startScope: 	'{' { 
					sprintf($$.name, "%s%d", tmpPre, tmpId++);
					pushScope(table); 
					printf("begin_%s:\n", $$.name);
				}
        ;

instBlock1:    	variableDecl
        | 		instBlock1 variableDecl
        ;

instBlock2:    	inst 
        | 		instBlock2 inst
        ;

inst:       expr ';'
        | 	';'
        |   instIf
        |   instWhile
        |   instFor
        |   instBreak
        |   instReturn
        |   instPrint
        |   instBlockList
        ;

instIf:     ifCond inst%prec LOWER_THAN_ELSE { printf("end_if_%s:\n", $1.name); }
        | 	ifCond inst ELSE inst
        ;

ifCond:		IF '(' expr ')' { 
				sprintf($$.name, "%d", ifId++);
				printf("\tbranch-if %s begin_if_%s end_if_%s\n", $3.name, $$.name, $$.name);
				printf("begin_if_%s:\n", $$.name);
			}
		;

instWhile:  WHILE '(' expr ')' inst
        ;

instFor:    FOR '(' expr';' expr';' expr')' inst
        | 	FOR '(' ';' expr';'expr')' inst
        | 	FOR '(' expr';' expr';' ')' inst
        | 	FOR '(' ';' expr';' ')' inst
        ;

instBreak:   	RETURN expr ';'
        | 		RETURN ';'
        ;

instReturn:    	BREAK ';'
        ;

instPrint:    	PRINT '(' list_instPrint ')' ';'
        ;

list_instPrint:     expr
        | 			list_instPrint ',' expr
        ;
   
expr:        valueL '=' expr 	{ 
									printf("\t%s := %s\n", $1.name, $3.name);
								}
        |    constant			{ 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %d\n", $$.name, $1.value.decimal_value); 
        						}
        |    valueL				{ }
        |    call				{ }
        |    '(' expr ')'		{ strcpy($$.name, $2.name); }
        |    expr '+' expr 		{ 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s + %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr '-' expr		{ 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s - %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr '*' expr		{ 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s * %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr '/' expr		{ 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s / %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr '%' expr		{ 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s %% %s\n", $$.name, $1.name, $3.name); 
        						}
        |    '-' expr			{ 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := -%s\n", $$.name, $2.name); 
        						}
        |    expr '<' expr		{ 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s < %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr LESSEQTHAN expr { 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s <= %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr '>' expr		{ 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s > %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr MOREEQTHAN expr { 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s >= %s\n", $$.name, $1.name, $3.name); 
        						}  
        |    expr EQUALCOMP expr { 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s == %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr DIFFCOMP expr { 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s != %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr AND expr 		{ 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s && %s\n", $$.name, $1.name, $3.name); 
        						}
        |    expr OR expr		{ 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := %s || %s\n", $$.name, $1.name, $3.name); 
        						}
        |    '!' expr			{ 
        							sprintf($$.name, "%s%d", tmpPre, tmpId++); 
        							printf("\t%s := !%s\n", $$.name, $2.name); 
        						}
        |    READINT '(' ')'
        |    READLINE '(' ')'
        ;

valueL:     ID 					{ strcpy($$.name, $1.name); }
        | 	expr '[' expr ']' 	{ sprintf($$.name, "%s[%s]", $1.name, $3.name); } // to fix
        ;

call:       ID '(' reals ')'
        | 	ID '('  ')'
        ;

reals:      expr 
        | 	reals ',' expr
        ;

constant:   CDEC	{ $$.type = decimal_type; $$.value.decimal_value = $1.value.decimal_value; }
		|	CHEX	{ $$.type = hex_type; }
        | 	CDOUBLE	{ $$.type = double_type; }
        | 	TRUE 	{ $$.type = bool_type; }
        | 	FALSE	{ $$.type = bool_type; }
        | 	CSTRING { $$.type = string_type; }
        | 	INULL	{ $$.type = pointer_type; }
        ;

%%

main(int argc, char **argv)
{
    if(argc != 2)
    {
        cout << "Bad arguments" << endl;
        return 1;
    }

    FILE *file = fopen(argv[1], "r");

    if(!file)
    {
        cout << "Invalid or wrong file" << endl;
        return 1;
    }

    yyin = file;	
    
    // push global scope
    pushScope(table);

    do
    {
        yyparse();
    }
    while(!feof(yyin));

    return 0;
}
